#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/bio.h>
#include <linux/blkdev.h>
#include <linux/types.h>
#include <linux/major.h>
#include <linux/file.h>
#include <linux/slab.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <asm/uaccess.h>


#define A_DISK  "/dev/sdb1"
#define B_DISK  "/dev/sdc1"

typedef struct my_dev
{
  char sdev[BDEVNAME_SIZE]; //include fs.h
  make_request_fn * old_mfn;
  request_queue_t * old_rq;
  struct block_device *bd;
}my_dev_t;

static my_dev_t a_disk, b_disk;

static void init_dev(my_dev_t *d, const char *devname)
{
	memset(d, 0, sizeof(my_dev_t));
	strcpy(d->sdev, devname);
	d->sdev[strlen(d->sdev)] = '\0';
	printk("\t devname: %s\n", d->sdev);
	return;
}

static struct block_device* get_block_device(my_dev_t *d)
{
  struct file *fp = NULL;
  struct block_device *bd = kmalloc(sizeof(struct block_device),GFP_KERNEL);
  if(!bd)
	{
	  printk("\tout of mem\n");
	  goto err;
	}
  memset(bd, 0 , sizeof(struct block_device));
  
  if (d == NULL)
	goto err;
  if(strlen(d->sdev) == 0)
	goto err;

  fp = filp_open(d->sdev,O_RDONLY,0600);
  if(IS_ERR(fp))
	{
	  printk("\t[get_block_device]: open dev %s failed.\n",d->sdev);
	  goto err;
	}
  memcpy(bd,fp->f_dentry->d_inode->i_bdev,sizeof(struct block_device));
  filp_close(fp,NULL);
  return bd;
 err:
  if(bd)
      {
	kfree(bd);
  	bd = NULL;
      }
  if(fp)
      {
	filp_close(fp,NULL);
	fp = NULL;
      }
  return bd;
}

static int set_mfn(my_dev_t *d , make_request_fn *mfn)
{
  char b[BDEVNAME_SIZE];
  request_queue_t *rq;
  
  struct block_device *bd = d->bd;
 
  if (!bd)
	{
	  printk("\t[set_mfn]: get_block_device failed.\n");
	  return -1;
	}

  rq = bdev_get_queue(bd);
  if (!rq)
	{
	  printk(KERN_ERR
			 "\t [set_mfn]: Trying to access "
			 "nonexistent block-device %s\n",
			 bdevname(bd, b));
	}
  d->old_rq = rq ;
  if(rq->make_request_fn == mfn)
	{
	  printk("\t [set_mfn]: already seted\n");
	  return 0;
	}
  d->old_mfn = rq->make_request_fn;
  rq->make_request_fn = mfn;
  return 0;
}

int restore_mfn(my_dev_t *d)
{
	if(d)
	{
		if(NULL != d->old_mfn)
			d->old_rq->make_request_fn = d->old_mfn;
		if(NULL != d->bd)
			kfree(d->bd);
	}
  return 0;
}
void print_bio(struct bio *bio)
{
	printk("bio = %x \n",bio);
	printk("\t %x \n",bio->bi_sector);
	printk("\t %x \n",bio->bi_next);
	printk("\t %x \n",bio->bi_bdev);
	printk("\t %ld \n",bio->bi_flags);
	printk("\t %ld \n",bio->bi_rw);
	printk("\t %d \n",bio->bi_vcnt);
	printk("\t %d \n",bio->bi_idx);
	printk("\t %d \n",bio->bi_phys_segments);
	printk("\t %d \n",bio->bi_hw_segments);
	printk("\t %d \n",bio->bi_size);
	printk("\t %x \n",bio->bi_io_vec);
	printk("\t \t %x \n",bio->bi_io_vec->bv_page);
	printk("\t \t %d \n",bio->bi_io_vec->bv_len);
	printk("\t \t %d \n",bio->bi_io_vec->bv_offset);

}

static int wfilter_make_request(request_queue_t *rq, struct bio *bio)
{
	int ret;
	struct bio *mbio = NULL;
	struct block_device *bd = b_disk.bd;
	printk("enter wfilter_make_request\n\n");
	if(bio_data_dir(bio) == WRITE)
	{
		mbio = bio_clone(bio, GFP_NOIO);
		if(mbio){
			mbio->bi_bdev = b_disk.bd;
		}	
	
	
		printk("write data to b_disk\n");
		print_bio(mbio);

		if (!bd)
		{
			printk("\t[set_mfn]: get_block_device failed.\n");
			return -1;
		}
	
		rq = bdev_get_queue(bd);
		printk("rq = %x\n",rq);
		if (!rq)
		{
			printk("get mirror disk rq fail!\n");
			return -1;
		}
		do{
			ret = rq->make_request_fn(rq, mbio);
		}while(ret);
	}
	a_disk.old_mfn(a_disk.old_rq, bio);
	
	return 0;
}

static int __init wfilter_init(void)
{
	printk("wfilter: start ... \n");
	
	/*init device*/
	init_dev(&a_disk, A_DISK);
	init_dev(&b_disk, B_DISK);
	if((a_disk.bd = get_block_device(&a_disk)) == NULL)
		return 0;
	if((b_disk.bd = get_block_device(&b_disk)) == NULL)
		return 0;
	
	printk("\t a_disk.bd = %x\n", a_disk.bd);
	printk("\t b_disk.bd = %x\n", b_disk.bd);

	if(set_mfn(&a_disk, wfilter_make_request)){
		printk("set_mfn failure !\n");
		return 0;
	}
	printk("set_mfn suc.\n");
	return 0;
}

static void wfilter_exit(void)
{
	restore_mfn(&a_disk);
	restore_mfn(&b_disk);
	printk("restore_mfn succ. bye!\n");
}

module_init(wfilter_init);
module_exit(wfilter_exit);
MODULE_LICENSE("GPL");
MODULE_ALIAS("wfilter"); /* wfilter */
